#include <stdio.h>  
#include <signal.h>
#include <iostream>
#include <sstream>

#include <zsystem/core/common/logdump.h>
#include <zsystem/core/common/md5.h>
#include <zsystem/core/common/event_types.h>
#include <zsystem/service/httpserver/httpserver.h>
#include <zsystem/core/lua/luaPacket.h>
#include <zsystem/core/networking/connection.h>
#include <zsystem/chatServer/chatserver_message_id.h>
#include <google/profiler.h>
#include "luaclient_config.h"
#include "luaclient.h"

#define Z_PROG_NAME "LuaClient"

#define Z_PROG_VERSION_MAJOR    0
#define Z_PROG_VERSION_MINOR    0
#define Z_PROG_VERSION_MICRO    1
#define Z_PROG_VERSION_INT      ( Z_PROG_VERSION_MAJOR<<16 | Z_PROG_VERSION_MINOR<<8 | Z_PROG_VERSION_MICRO )
#define Z_PROG_VERSION          "0.0.1"

zsystem::luaPacket* g_lua = Z_NULL;

Z_BOOL g_exit=Z_FALSE;
zsystem::Z_EV_BASE* g_base = Z_NULL;
struct timeval g_tv;
zsystem::Z_EV* g_timerEvent = Z_NULL;
Z_I32 g_step = 0;
class luaTcpClient;
luaTcpClient* g_client = Z_NULL;

///////////////////////////////////////////////////////////////////////////////
// to Lua functions
const Z_BOOL 
connected(zsystem::tcpConnection* connection)
{
  zsystem::luaPacket* lua = g_lua;
	Z_ASSERT_RETURN(lua->findLuaGlobalFunction("on_connected"), Z_FALSE);
  lua_State* L = lua->get();
  luaTcpClient* client = dynamic_cast<luaTcpClient*>(connection);
  if ( client == Z_NULL )
    return Z_FALSE;
  lua_createtable(L, 0, 1);
	lua_pushstring(L, "connId");
  lua_pushinteger(L, client->getId());
	lua_rawset(L, -3);

	if ( lua->callLuaScript(1) != 1 ) 
  {
    LOG(WARNING) << "zsystem::luaPacket::connected call_lua_script failed!";
    return Z_FALSE;
	}
  return lua_toboolean(L, -1) != 0;
}

const Z_BOOL 
receivedPacket(zsystem::tcpConnection* connection, const Z_U16 seqId, const Z_U16 msgId, 
               char* const buffer, const Z_U16 len)
{
  zsystem::luaPacket* lua = g_lua;
	Z_ASSERT_RETURN(lua->findLuaGlobalFunction("received_packet"), Z_FALSE);
  lua_State* L = lua->get();

	lua_createtable(L, 0, 4);
	lua_pushstring(L, "connId");
  lua_pushinteger(L, connection->getId());
	lua_rawset(L, -3);

	lua_pushstring(L, "seqId");
	lua_pushinteger(L, seqId);
	lua_rawset(L, -3);

	lua_pushstring(L, "msgId");
	lua_pushinteger(L, msgId);
	lua_rawset(L, -3);

	lua_pushstring(L, "data");
	lua_pushlstring(L, (const char *)buffer, len);	
	lua_rawset(L, -3);

	if (lua->callLuaScript(1) != 2) 
  {
    LOG(WARNING) << "zsystem::luaPacket::receivedPacket call_lua_script failed!";
    return Z_FALSE;
	}

  int luaOK = lua_toboolean(L, -2);
	if (luaOK) 
  {
    const char* respData = lua_tostring(L, -1);
    return connection->response(seqId, msgId, respData, strnlen(respData, 65535));
	}
	else {
    return Z_FALSE;
	}
  return Z_TRUE;
}
//////////////////////////////////////////////////////////////////////////////////
// from lua functions
int LUA_connect(lua_State* L)
{
  Z_DUMP_LOG("LUA_connect");
  if (lua_gettop(L) < 1) 
  {
		lua_pushboolean(L, 0);
		return 1;
	}
  const char* addr = lua_tostring(L, 1);

  Z_BOOL ret(Z_FALSE);

  if ( g_client != Z_NULL){ 
    ret = g_client->connect(addr);
  }
  if ( ret )
  {
    Z_DUMP_LOG("connection ok");
  }
  else
  {
    Z_DUMP_LOG("connection failed");
  }
  lua_pushboolean(L, 1);
	return 0;
}

int LUA_disconnect(lua_State* L)
{
  g_client->close();
  lua_pushboolean(L, 1);
	return 0;
}

int LUA_request(lua_State* L)
{
  Z_DUMP_LOG("LUA_request");
  if (lua_gettop(L) < 2) 
  {
		lua_pushboolean(L, 0);
		return 1;
	}

  Z_BOOL ret(Z_FALSE);
  Z_I32 msgId = (Z_I32)luaL_checknumber(L, 1);
  size_t jsonDataLen(0);
  const char* jsonData = luaL_checklstring(L, 2, &jsonDataLen);
  if ( jsonData != Z_NULL && jsonDataLen < zsystem::packet::MAX_PACKET_SIZE )
  {
    Z_DUMP_LOG("Json: " << jsonData << ", len: " << jsonDataLen);
    ret = g_client->send(msgId, jsonData, jsonDataLen);
  }

  if ( ret )
  {
    Z_DUMP_LOG("request ok");
  }
  else
  {
    Z_DUMP_LOG("request failed");
  }
	return 0;
}

int LUA_reponse(lua_State* L)
{
	return 0;
}
const struct luaL_Reg lua_networking_lib[] = {
		{"connect", LUA_connect},
		{"disconnect", LUA_disconnect},
		{"request", LUA_request},
		{"response", LUA_reponse},
		{NULL, NULL}
};
//////////////////////////////////////////////////////////////////////////////
// Callbacks
void
sigint(int s) 
{
  g_exit = true;
  event_base_loopbreak(g_base);
}

void dumpLog(Z_PTR arg)
{
  while(!g_exit)
  {
    zsystem::ssleep(5);
    Z_DUMP_LOG("testing");
    google::FlushLogFiles(INFO);
  }
}

void 
onTimer(Z_SOCKET_HANDLE fd, Z_I16 ev, Z_PTR arg)
{
  // Todo: some timer
  if (g_step == 0)
  {
    g_step = 1;
    g_lua->call("do_test");
  }
  evtimer_add(g_timerEvent, &g_tv);
}

#pragma warning(disable:4996)
void setLogDestination(std::string& logPath)
{
  char buffer[1024];
  sprintf(buffer, "%s%s%s_%s_", logPath.c_str(), Z_PATH_SLASH, Z_PROG_NAME, "INFO");
  google::SetLogDestination(INFO, buffer);
  sprintf(buffer, "%s%s%s_%s_", logPath.c_str(), Z_PATH_SLASH, Z_PROG_NAME, "WARNING");
  google::SetLogDestination(WARNING, buffer);
  sprintf(buffer, "%s%s%s_%s_", logPath.c_str(), Z_PATH_SLASH, Z_PROG_NAME, "ERROR");
  google::SetLogDestination(ERROR, buffer);
}
#pragma warning(default:4996)

void 
tcpClientCallback(zsystem::listenerContainer* container, zsystem::tcpConnection* connection, 
                  const Z_U16 seqId, const Z_U16 msgId, char* const buffer, const Z_U16 len)
{
  Z_DUMP_LOG("tcpClientCallback( seqId = " << seqId << ", " << msgId << ")");
  receivedPacket(connection, seqId, msgId, buffer, len);
}

int
main(int argc, char ** argv)
{

  std::string version(Z_PROG_VERSION);
  google::SetVersionString(version);
  google::ParseCommandLineFlags(&argc, &argv, true);

  google::InitGoogleLogging(argv[0]);

  // Todo: Load config
  setLogDestination(FLAGS_LogPath);
#ifdef WIN32
  WSADATA wsaData;  
  DWORD Ret;  
  if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)  
  {
    printf("WSAStartup failed with error %d\n", Ret);  
    return -1;  
  }
  CHECK_EQ(evthread_use_windows_threads(), 0);
#else
  CHECK_EQ(evthread_use_pthreads(), 0);
#endif
//  event_set_log_callback(Z_LIBEVENT_LOG_PRINT);
  event_set_log_callback(Z_LIBEVENT_LOG_DUMMY);

  event_set_mem_functions(Z_MALLOC_FUNC, Z_REALLOC_FUNC, Z_FREE_FUNC);

  g_lua = Z_NEW(zsystem::luaPacket)();
  Z_ASSERT_ALL(g_lua != Z_NULL);

  Z_ASSERT_ALL(g_lua->init(FLAGS_ScriptFolderPath.c_str(), lua_networking_lib, Z_NULL));

  // Todo: start client
  g_base = event_base_new();

  g_tv.tv_sec = 1;
	g_tv.tv_usec = 0;
  g_timerEvent = (zsystem::Z_EV*)Z_MALLOC(sizeof(zsystem::Z_EV));
  evtimer_assign( g_timerEvent, g_base, onTimer, Z_NULL);
  evtimer_add(g_timerEvent, &g_tv);

  zsystem::thread* dumpLogThread = Z_NEW(zsystem::thread)(dumpLog, Z_NULL);
  Z_ASSERT_ALL(dumpLogThread->start());

  g_client = Z_NEW(luaTcpClient)(FLAGS_Id, tcpClientCallback, FLAGS_SendBufferSize, FLAGS_ReceiveBufferSize, g_base);
  //if ( !g_client->connect(FLAGS_ChatServerAddress.c_str()) )
  //{
  //  LOG(WARNING) << "Connect chat server failed!";  
  //  Z_SAFE_DELETE(g_client, ~luaTcpClient);
  //  return -1;
  //}

  Z_DUMP("Client start running");
  event_base_loop(g_base, 0);
  Z_DUMP("Client stop");

  dumpLogThread->join();
  Z_SAFE_DELETE(dumpLogThread, ~thread);

  Z_SAFE_DELETE(g_client, ~luaTcpClient);
  event_base_free(g_base);


  Z_SAFE_DELETE(g_lua, ~luaPacket);

  Z_FREE(g_timerEvent);

#ifdef WIN32
  WSACleanup();  
#endif

//  Z_DUMP_LOG("Server exit success");
  google::FlushLogFiles(3);
  google::ShutdownGoogleLogging();

//  Z_DUMP_LOG("To shutdown google log and flags");
  google::ShutDownCommandLineFlags();

  return 0;  
}  
